#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <common.h>
#include <session.h>
#include <parseconf.h>
#include <tunable.h>
#include <hash.h>
#include <ftpproto.h>
#include <ftpcode.h>

extern session_t *p_sess;
static uint32_t s_children;

void check_limits(session_t *sess);

void handle_sigchld(int sig);

uint32_t hash_func(uint32_t buckets,void *key);
//返回连接数
uint32_t handle_ip_count(void *ip);
void drop_ip_count(void *ip);

//ip和连接数对应的hash表
static hash_t *s_ip_count_hash;
static hash_t *s_pid_ip_hash;

int
main(int argc,char *argv[])
{
	//读取配置
	parseconf_load_file(MINIFTP_CONF);
	//要将进程转换为守护进程
	#ifdef _DEBUG
	daemon(1,1);
	#else
	daemon(0,0);
	#endif
	DEBUG_PRINT("tunable_pasv_enable=%d\n",tunable_pasv_enable);
	DEBUG_PRINT("tunable_port_enable=%d\n",tunable_port_enable);
	DEBUG_PRINT("tunable_listen_port=%d\n",tunable_listen_port);
	DEBUG_PRINT("tunable_max_clients=%d\n",tunable_max_clients);
	DEBUG_PRINT("tunable_max_per_ip=%d\n",tunable_max_per_ip);
	DEBUG_PRINT("tunable_accept_timeout=%d\n",tunable_accept_timeout);
	DEBUG_PRINT("tunable_connect_timeout=%d\n",tunable_connect_timeout);
	DEBUG_PRINT("tunable_idle_session_timeout=%d\n",tunable_idle_session_timeout);
	DEBUG_PRINT("tunable_data_connection_timeout=%d\n",tunable_data_connection_timeout);
	DEBUG_PRINT("tunable_local_umask=%d\n",tunable_local_umask);
	DEBUG_PRINT("tunable_upload_max_rate=%d\n",tunable_upload_max_rate);
	DEBUG_PRINT("tunable_download_max_rate=%d\n",tunable_download_max_rate);
	DEBUG_PRINT("tunable_listen_address=%s\n",tunable_listen_address);
	//注册信号，因为主循环中不能使用waitpid来等待子进程结束（因为要考虑并发性）
	//所以只能忽略子进程退出的时候发出的SIGCHLD信号
	signal(SIGCHLD,handle_sigchld);
	//首先要root用户才能启动这个程序
	if(0 != getuid()) {
		fprintf(stderr,
			"miniftpd: must be started as root\n");
		exit(EXIT_FAILURE);
	}
	//作为服务端，都应该有基本的套接字初始化函数
	int listenfd = tcp_server_sock_init(tunable_listen_address,tunable_listen_port);
	//接收客户端的连接
	int connfd;
	pid_t pid;
	session_t sess = {
		0,-1,"","",
		"",NULL,-1,
		-1,0,0,0,0,0,
		-1,-1,0,0,NULL,
		0,0,0,
	};
	sess.bw_upload_rate_max = tunable_upload_max_rate;
	sess.bw_download_rate_max = tunable_download_max_rate;

	s_ip_count_hash = hash_alloc(253,hash_func);
	s_pid_ip_hash = hash_alloc(253,hash_func);
	struct sockaddr_in connaddr;
	socklen_t connaddr_len = sizeof(struct sockaddr_in);
	uint32_t ip;
	while(1) {
		connfd = accept_timeout(listenfd,&connaddr,&connaddr_len,0);
		if(connfd < 0) {
			ERR_EXIT("accept_timeout");
		}
		ip = connaddr.sin_addr.s_addr;
		//先根据ip作为key看有没有对应的记录
		//有就将count数取出来加1
		//不过这里有个问题是当ip数增加后原来
		//的子进程得不到通知使用STAT命令显示
		//会有误
		++s_children;
		sess.num_clients = s_children;
		sess.num_this_ip = handle_ip_count(&ip);
		//创建进程进行ftp服务
		pid = fork();
		if(pid < 0) {
			--s_children;
			ERR_EXIT("fork");
		}
		if(pid == 0) {
			//child
			//防止nobody子进程中继承这个处理函数，
			//本来是不需要子进程处理的
			signal(SIGCHLD,SIG_IGN);
			close(listenfd);
			sess.ctrl_fd = connfd;
			check_limits(&sess);
			//开始进行服务会话
			begin_session(&sess);
		} else {
			//需要往hash表添加pid和ip的对应关系
			hash_add_entry(s_pid_ip_hash,&pid,sizeof(pid),
									&ip,sizeof(uint32_t));
			close(connfd);
		}
	}
	return 0;
}

void
check_limits(session_t *sess)
{
	const char *presponse_str;
	if(tunable_max_clients > 0 && \
		sess->num_clients > tunable_max_clients) {
		//421用信号通知s_children--
		presponse_str = \
			FTP_RESPONSE_STR(FTP_TOO_MANY_USERS,
			"There are too many connected users,please try later.");
		writen(sess->ctrl_fd,presponse_str,strlen(presponse_str));
		exit(EXIT_FAILURE);
	}
	//检测每ip连接数是否超过
	if(tunable_max_per_ip > 0 && 
		sess->num_this_ip > tunable_max_per_ip) {
		presponse_str = FTP_RESPONSE_STR(FTP_IP_LIMIT,
			"There are too many connected from your internet address.");
		writen(sess->ctrl_fd,presponse_str,strlen(presponse_str));
		exit(EXIT_FAILURE);
	}
}

void
handle_sigchld(int sig)
{
	pid_t pid;
	//signal(SIGCHLD,handle_sigchld);
	//这里设置的WNOHANG使得waitpid如果接收不到结束的
	//进程号就立刻返回0避免阻塞
	while((pid = waitpid(-1,NULL,WNOHANG)) > 0) {
		--s_children;
		//每IP连接数的限制
		//需要通过pid来获取ip再通过ip获取count
		uint32_t *ip = hash_lookup_entry(s_pid_ip_hash,
									&pid,sizeof(pid));
		if(NULL == ip) {
			continue;
		}
		drop_ip_count(ip);
		//既然进程结束了
		hash_free_entry(s_pid_ip_hash,&pid,sizeof(pid));
	}
}

uint32_t hash_func(uint32_t buckets,void *key)
{
	uint32_t *number = (uint32_t *)key;

	return (*number) % buckets;
}

uint32_t handle_ip_count(void *ip)
{
	uint32_t count = 0;
	uint32_t *p_count = \
		(uint32_t *)hash_lookup_entry(s_ip_count_hash,
									ip,sizeof(uint32_t));

	if(NULL == p_count) {
		count = 1;
		hash_add_entry(s_ip_count_hash,ip,
			sizeof(uint32_t),&count,sizeof(uint32_t));
	} else {
		count = ++(*p_count);
	}
	return count;
}

void drop_ip_count(void *ip)
{
	uint32_t *p_count = \
		(uint32_t *)hash_lookup_entry(s_ip_count_hash,
									ip,sizeof(uint32_t));
	if(NULL == p_count) {
		return;
	} else {
		if(*p_count <= 0)
			return;
		--(*p_count);
	}
	if(0 == *p_count) {
		hash_free_entry(s_ip_count_hash,ip,sizeof(uint32_t));
	}
}
